//
//  Framework for a raytracer
//  File: scene.cpp
//
//  Created for the Computer Science course "Introduction Computer Graphics"
//  taught at the University of Groningen by Tobias Isenberg.
//
//  Authors:
//    Maarten Everts
//    Jasper van de Gronde
//	  Zhe Sun
//
//  This framework is inspired by and uses code of the raytracer framework of 
//  Bert Freudenberg that can be found at
//  http://isgwww.cs.uni-magdeburg.de/graphik/lehre/cg2/projekt/rtprojekt.html 
//

#include "scene.h"
#include "material.h"

Color Scene::trace(const Ray &ray)
{
    // Find hit object and distance
    Hit min_hit(std::numeric_limits<double>::infinity(),Vector());
    Object *obj = NULL;
    for (unsigned int i = 0; i < objects.size(); ++i) {
        Hit hit(objects[i]->intersect(ray));
        if (hit.t<min_hit.t) {
            min_hit = hit;
            obj = objects[i];
        }
    }

    // No hit? Return background color.
    if (!obj) return Color(0.0, 0.0, 0.0);

    Material *material = obj->material;            //the hit objects material
    Point hit = ray.at(min_hit.t);                 //the hit point
    Vector N = min_hit.N;                          //the normal at hit point
    Vector V = -ray.D;                             //the view vector

    /****************************************************
    * This is where you should insert the color
    * calculation (Phong model).
    *
    * Given: material, hit, N, V, lights[]
    * Sought: color
    *
    * Hints: (see triple.h)
    *        Triple.dot(Vector) dot product
    *        Vector+Vector      vector sum
    *        Vector-Vector      vector difference
    *        Point-Point        yields vector
    *        Vector.normalize() normalizes vector, returns length
    *        double*Color        scales each color component (r,g,b)
    *        Color*Color        dito
    *        pow(a,b)           a to the power of b
    ****************************************************/

// formular from the book Page 83    
#if 0    
    Color color = material->color;          
    
    // Ambient
    Color totalColor = material->ka * color * lights[0]->color;
    
    // Iterate all the lights
    vector<Light*>::iterator iter;
    for (iter = lights.begin(); iter != lights.end(); ++iter){
      Point lPos = (*iter)->position;
      Color lColor = (*iter)->color;
      
      // get vector from the hit point to the light
      Vector l = (lPos - hit).normalized();
      
      // get half vector between l(hit point to light) and V(hit point to eye)
      Vector h = (V + l).normalized();
      
      // max(0, (n.l))
      double dot_N_l = (N.dot(l) > 0) ? N.dot(l) : 0;
      // max(0, (n.h))
      double dot_N_h = (N.dot(h) > 0) ? N.dot(h) : 0;
      
      // Diffuse component
      totalColor += lColor * color * material->kd * dot_N_l;
      
      // specular component
      // Attention: Specular does not depend on material color!!!
      totalColor += lColor * material->ks * pow(dot_N_h, material->n);
    }
    return totalColor;
#else
    // implementation according to the tutorial sheet lab 1
    Color color = material->color;          
    
    // Ambient
    Color totalColor = material->ka * color * lights[0]->color;
    
    // Iterate all the lights
    vector<Light*>::iterator iter;
    for (iter = lights.begin(); iter != lights.end(); ++iter){
      Point lPos = (*iter)->position;
      Color lColor = (*iter)->color;
      
      // get vector from the hit point to the light
      Vector L = (lPos - hit).normalized();
      
      // get the reflect vector 
      Vector R = 2*(L.dot(N))*N - L;
      
      // max(0, (n.l))
      double dot_N_L = (N.dot(L) > 0) ? N.dot(L) : 0;
      // max(0, (R.V))
      double dot_R_V = (R.dot(V) > 0) ? R.dot(V) : 0;
      
      // Diffuse component
      totalColor += lColor * color * material->kd * dot_N_L;
      
      // specular component
      // Attention: Specular does not depend on material color!!!
      totalColor += lColor * material->ks * pow(dot_R_V, material->n);
    }
    return totalColor;
#endif
}

void Scene::render(Image &img)
{
    int w = img.width();
    int h = img.height();
    for (int y = 0; y < h; y++) {
        for (int x = 0; x < w; x++) {
            Point pixel(x+0.5, h-1-y+0.5, 0);
            Ray ray(eye, (pixel-eye).normalized());
            Color col = trace(ray);
            col.clamp();
            img(x,y) = col;
        }
    }
}

void Scene::addObject(Object *o)
{
    objects.push_back(o);
}

void Scene::addLight(Light *l)
{
    lights.push_back(l);
}

void Scene::setEye(Triple e)
{
    eye = e;
}
